#include "shader.h"

Shader::Shader()
{
	this->vertex_shader_id = BAD_GL_VALUE;
	this->fragment_shader_id = BAD_GL_VALUE;
	this->program_id = BAD_GL_VALUE;

	this->modelview_matrix_handle = BAD_GL_VALUE;
	this->projection_matrix_handle = BAD_GL_VALUE;
	this->normal_matrix_handle = BAD_GL_VALUE;
	this->mvp_handle = BAD_GL_VALUE;

	this->image_map_handle = BAD_GL_VALUE;
	this->size_handle = BAD_GL_VALUE;
	this->source_handle = BAD_GL_VALUE;
	this->destination_handle = BAD_GL_VALUE;
}

GLuint Shader::GetModelviewMatrixHandle()
{
	return modelview_matrix_handle;
}

GLuint Shader::GetProjectionMatrixHandle()
{
	return projection_matrix_handle;
}

GLuint Shader::GetNormalMatrixHandle()
{
	return normal_matrix_handle;
}

GLuint Shader::GetMVPHandle()
{
	return mvp_handle;
}

GLuint Shader::GetImageMapHandle()
{
	return image_map_handle;
}

GLuint Shader::GetSizeHandle()
{
	return size_handle;
}

GLuint Shader::GetSourceHandle()
{
	return source_handle;
}

GLuint Shader::GetDestinationHandle()
{
	return destination_handle;
}

void Shader::Use()
{
	if (this->program_id != -1)
	{
		// reference: http://www.opengl.org/sdk/docs/man/xhtml/glUseProgram.xml
		glUseProgram(this->program_id);
	}
}

bool Shader::Initialize(char * vertex_shader_file, char * fragment_shader_file)
{
	if (CheckGLErrors())
	{
		return false;
	}

	GLint check_value;

	// initialize the vertex shader
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glCreateShader.xml
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glCompileShader.xml
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glGetShader.xml
	this->vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);
	this->LoadShader(vertex_shader_file, this->vertex_shader_id);
	glCompileShader(this->vertex_shader_id);
	glGetShaderiv(this->vertex_shader_id, GL_COMPILE_STATUS, &check_value);
	
	if (check_value != GL_TRUE)
	{
		return false;
	}

	if (CheckGLErrors())
	{
		return false;
	}

	// initialize the fragment shader
	this->fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
	this->LoadShader(fragment_shader_file, this->fragment_shader_id);
	glCompileShader(this->fragment_shader_id);
	glGetShaderiv(this->fragment_shader_id, GL_COMPILE_STATUS, &check_value);

	if (check_value != GL_TRUE)
	{
		return false;
	}

	// initialize the program
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glCreateProgram.xml
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glAttachShader.xml
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glLinkProgram.xml
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glGetProgram.xml
	this->program_id = glCreateProgram();
	glAttachShader(this->program_id, this->vertex_shader_id);
	glAttachShader(this->program_id, this->fragment_shader_id);
	glLinkProgram(this->program_id);
	glGetProgramiv(program_id, GL_LINK_STATUS, &check_value);

	if (check_value != GL_TRUE)
	{
		return false;
	}
	
	// flag both shaders for deletion
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glDeleteShader.xml
	glDeleteShader(vertex_shader_id);
	glDeleteShader(fragment_shader_id);	

	glUseProgram(this->program_id);	

	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glGetUniformLocation.xml
	/*
	this->modelview_matrix_handle = glGetUniformLocation(this->program_id, (const GLchar *) "modelview_matrix");
	this->projection_matrix_handle = glGetUniformLocation(this->program_id, (const GLchar *) "projection_matrix");
	this->normal_matrix_handle = glGetUniformLocation(this->program_id, (const GLchar *) "normal_matrix");
	this->mvp_handle = glGetUniformLocation(this->program_id, (const GLchar *) "mvp");
	*/
	
	this->size_handle = glGetUniformLocation(this->program_id, (const GLchar *) "framebuffer_size");
	this->source_handle = glGetUniformLocation(this->program_id, (const GLchar *) "source");
	this->destination_handle = glGetUniformLocation(this->program_id, (const GLchar *) "destination");

	glUseProgram(0);

	if (CheckGLErrors())
	{
		return false;
	}

	return true;
}

void Shader::TakeDown()
{
	// if the shader was never initialized, nothing needs to be done to take it down
	if (this->program_id == (GLuint) -1)
	{
		return;
	}

	GLint num_attached_shaders;
	GLsizei size;

	glGetProgramiv(this->program_id, GL_ATTACHED_SHADERS, &num_attached_shaders);

	if (num_attached_shaders > 0)
	{
		// reference: http://www.opengl.org/sdk/docs/man/xhtml/glGetAttachedShaders.xml
		GLuint * shader_list = new GLuint[num_attached_shaders];
		glGetAttachedShaders(this->program_id, num_attached_shaders, &size, shader_list);

		for (GLsizei i = 0; i < size; i++)
		{
			// all shaders were marked for deletion immediately after being created
			// reference: http://www.opengl.org/sdk/docs/man/xhtml/glDetachShader.xml
			glDetachShader(this->program_id, shader_list[i]);
		}

		delete [] shader_list;
	}

	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glDeleteProgram.xml
	glDeleteProgram(this->program_id);
	this->program_id = (GLuint) -1;
}

bool Shader::CheckGLErrors()
{
	bool error_found = false;

	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glGetError.xml
	while (glGetError() != GL_NO_ERROR)
	{
		error_found = true;
	}

	return error_found;
}

bool Shader::LoadShader(const char * file_name, GLuint shader_id)
{
	if (file_name == NULL)
	{
		return false;
	}

	if (CheckGLErrors())
	{
		return false;
	}

	FILE * file_handle = NULL;
	
	// reference: http://msdn.microsoft.com/en-us/library/z5hh6ee9%28v=vs.80%29.aspx
	fopen_s(&file_handle, file_name, "rb");

	if (file_handle == NULL)
	{
		return false;
	}

	// reference: http://www.cplusplus.com/reference/clibrary/cstdio/fseek/
	// reference: http://www.cplusplus.com/reference/clibrary/cstdio/ftell/
	fseek(file_handle, 0, SEEK_END);
	int length = ftell(file_handle);
	fseek(file_handle, 0, SEEK_SET);

	GLubyte * buffer = new GLubyte[length + 1];

	// reference: http://www.cplusplus.com/reference/clibrary/cstdio/fread/
	// reference: http://www.cplusplus.com/reference/clibrary/cstdio/fclose/
	fread(buffer, sizeof(GLubyte), length, file_handle);
	fclose(file_handle);
	buffer[length] = 0;

	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glShaderSource.xml
	glShaderSource(shader_id, 1, (const char **) &buffer, NULL);
	delete [] buffer;

	if (CheckGLErrors())
	{
		return false;
	}

	return true;
}